/*
 * Copyright (C) 2012 Matt Broadstone
 * Contact: http://code.google.com/p/qconnman/
 *
 * This file is part of the QConnman Library.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

#include "serviceinterface.h"
#include "service.h"

ConfigurableObject::ConfigurableObject(Service *parent)
    : QObject(parent)
{
}

void ConfigurableObject::apply()
{
    Service *service = qobject_cast<Service*>(parent());
    if (!service) {
        qDebug() << Q_FUNC_INFO << "invalid parent";
        return;
    }

    QString propertyName(objectName());
    if (!propertyName.endsWith(QLatin1String("Configuration"))) {
        qDebug() << Q_FUNC_INFO << "object is not configurable";
        return;
    }

    QVariantMap data;
    const QMetaObject *mo = metaObject();
    for (int i = mo->propertyOffset(); i < mo->propertyCount(); i++) {
        QMetaProperty mp = mo->property(i);
	QVariant propertyData = mp.read(this);
	if (propertyData.isValid() && !propertyData.isNull())
	    data.insert(mp.name(), propertyData);
    }

    QDBusPendingReply<> reply = service->m_serviceInterface->SetProperty(propertyName, QDBusVariant(data));
    reply.waitForFinished();
    if (reply.isError())
      qDebug() << Q_FUNC_INFO << "error: " << reply.error();
}


Service::Service(const ObjectPropertyData &info, QObject *parent)
    : ConnManObject(parent),
      m_objectPath(info.path),
      m_strength(0),
      m_favorite(false),
      m_immutable(false),
      m_autoConnect(false),
      m_roaming(false),
      m_ethernet(0),
      m_ethernetConfiguration(0),
      m_ipv4(0),
      m_ipv4Configuration(0),
      m_ipv6(0),
      m_ipv6Configuration(0),
      m_proxy(0),
      m_proxyConfiguration(0),
      m_provider(0),
      m_providerConfiguration(0)
{
    // complex properties
    m_ethernet = new EthernetData(this);
    m_ethernet->setObjectName("Ethernet");
    m_ethernetConfiguration = new EthernetData(this);
    m_ethernetConfiguration->setObjectName("Ethernet.Configuration");

    m_ipv4 = new IPV4Data(this);
    m_ipv4->setObjectName("IPv4");
    m_ipv4Configuration = new IPV4Data(this);
    m_ipv4Configuration->setObjectName("IPv4.Configuration");

    m_ipv6 = new IPV6Data(this);
    m_ipv6->setObjectName("IPv6");
    m_ipv6Configuration = new IPV6Data(this);
    m_ipv6Configuration->setObjectName("IPv6.Configuration");

    m_proxy = new ProxyData(this);
    m_proxy->setObjectName("Proxy");
    m_proxyConfiguration = new ProxyData(this);
    m_proxyConfiguration->setObjectName("Proxy.Configuration");

    m_provider = new ProviderData(this);
    m_provider->setObjectName("Provider");
    m_providerConfiguration = new ProviderData(this);
    m_providerConfiguration->setObjectName("Provider.Configuration");

    m_serviceInterface =
        new ServiceInterface("net.connman", info.path.path(), QDBusConnection::systemBus(), this);
    if (!m_serviceInterface->isValid()) {
        qDebug() << "unable to connect to service at path: " << info.path.path();
        return;
    }

    QObject::connect(m_serviceInterface, SIGNAL(PropertyChanged(QString,QDBusVariant)),
                                   this, SLOT(propertyChanged(QString,QDBusVariant)));

    // set the initial properties
    foreach (QString property, info.properties.keys()) {
        qDebug() << property << info.properties.value(property);
        propertyChanged(property, QDBusVariant(info.properties.value(property)));
    }
}

Service::~Service()
{
}

void Service::propertyChanged(const QString &property, const QDBusVariant &value)
{
    // are we working with a complex object?
    QObject *dataObject = 0;
    if (property == QLatin1String("IPv4"))
        dataObject = m_ipv4;
    else if (property == QLatin1String("IPv4.Configuration"))
        dataObject = m_ipv4Configuration;
    else if (property == QLatin1String("IPv6"))
        dataObject = m_ipv6;
    else if (property == QLatin1String("IPv6.Configuration"))
        dataObject = m_ipv6Configuration;
    else if (property == QLatin1String("Ethernet"))
        dataObject = m_ethernet;
    else if (property == QLatin1String("Ethernet.Configuration"))
        dataObject = m_ethernetConfiguration;
    else if (property == QLatin1String("Proxy"))
        dataObject = m_proxy;
    else if (property == QLatin1String("Proxy.Configuration"))
        dataObject = m_proxyConfiguration;
    else if (property == QLatin1String("Provider"))
        dataObject = m_provider;
    else if (property == QLatin1String("Provider.Configuration"))
        dataObject = m_providerConfiguration;

    if (dataObject) {
        QVariantMap data = qdbus_cast<QVariantMap>(value.variant());
        foreach (QString key, data.keys())
            setObjectProperty(dataObject, key, data.value(key));
        return;
    }

    // otherwise normal behavior
    ConnManObject::propertyChanged(property, value);
}

QString Service::stateInternal() const
{
    return m_state;
}

QDBusObjectPath Service::objectPath() const
{
    return m_objectPath;
}

Service::ServiceState Service::state() const
{
    if (m_state == QLatin1String("idle"))
        return IdleState;
    if (m_state == QLatin1String("failure"))
        return FailureState;
    if (m_state == QLatin1String("association"))
        return  AssociationState;
    if (m_state == QLatin1String("configuration"))
        return ConfigurationState;
    if (m_state == QLatin1String("configuration"))
        return ConfigurationState;
    if (m_state == QLatin1String("ready"))
        return ReadyState;
    if (m_state == QLatin1String("disconnect"))
        return DisconnectState;
    if (m_state == QLatin1String("online"))
        return OnlineState;
    return UndefinedState;
}

void Service::setStateInternal(const QString &state)
{
    m_state = state;
    Q_EMIT dataChanged();
}

QString Service::error() const
{
    return m_error;
}

void Service::setErrorInternal(const QString &error)
{
    m_error = error;
    Q_EMIT dataChanged();
}

QString Service::name() const
{
    return m_name;
}

void Service::setNameInternal(const QString &name)
{
    m_name = name;
    Q_EMIT dataChanged();
}

QString Service::type() const
{
    return m_type;
}

void Service::setTypeInternal(const QString &type)
{
    m_type = type;
    Q_EMIT dataChanged();
}

QStringList Service::security() const
{
    return m_security;
}

void Service::setSecurityInternal(const QStringList &security)
{
    m_security = security;
    Q_EMIT dataChanged();
}

quint8 Service::strength() const
{
    return m_strength;
}

void Service::setStrengthInternal(quint8 strength)
{
    m_strength = strength;
    Q_EMIT dataChanged();
}

bool Service::isFavorite() const
{
    return m_favorite;
}

void Service::setFavoriteInternal(bool favorite)
{
    m_favorite = favorite;
    Q_EMIT dataChanged();
}

bool Service::isImmutable() const
{
    return m_immutable;
}

void Service::setImmutableInternal(bool immutable)
{
    m_immutable = immutable;
    Q_EMIT dataChanged();
}

bool Service::isAutoConnect() const
{
    return m_autoConnect;
}

void Service::setAutoConnectInternal(bool autoConnect)
{
    m_autoConnect = autoConnect;
    Q_EMIT dataChanged();
}

void Service::setAutoConnect(bool autoConnect)
{
    QDBusPendingReply<> reply =
        m_serviceInterface->SetProperty("AutoConnect", QDBusVariant(autoConnect));
    reply.waitForFinished();
    if (reply.isError())
        qDebug() << "could not set property: " << reply.error().message();
}

bool Service::isRoaming() const
{
    return m_roaming;
}

void Service::setRoamingInternal(bool roaming)
{
    m_roaming = roaming;
    Q_EMIT dataChanged();
}

QStringList Service::nameservers() const
{
    return m_nameservers;
}

void Service::setNameserversInternal(const QStringList &servers)
{
    m_nameservers = servers;
    Q_EMIT dataChanged();
}

QStringList Service::nameserversConfiguration() const
{
    return m_nameserversConfiguration;
}

void Service::setNameserversConfigurationInternal(const QStringList &nameServers)
{
    m_nameserversConfiguration = nameServers;
    Q_EMIT dataChanged();
}

void Service::setNameserversConfiguration(const QStringList &nameServers)
{
    QDBusPendingReply<> reply = m_serviceInterface->SetProperty("Nameservers.Configuration", QDBusVariant(nameServers));
    reply.waitForFinished();
    if (reply.isError())
        qDebug() << Q_FUNC_INFO << "error: " << reply.error();
}


QStringList Service::timeservers() const
{
    return m_timeservers;
}

void Service::setTimeserversInternal(const QStringList &servers)
{
    m_timeservers = servers;
    Q_EMIT dataChanged();
}

QStringList Service::timeserversConfiguration() const
{
    return m_timeserversConfiguration;
}

void Service::setTimeserversConfigurationInternal(const QStringList &timeServers)
{
    m_timeserversConfiguration = timeServers;
    Q_EMIT dataChanged();
}

void Service::setTimeserversConfiguration(const QStringList &timeServers)
{
    QDBusPendingReply<> reply = m_serviceInterface->SetProperty("Timeservers.Configuration",  QDBusVariant(timeServers));
    reply.waitForFinished();
    if (reply.isError())
        qDebug() << Q_FUNC_INFO << "error: " << reply.error();
}


QStringList Service::domains() const
{
    return m_domains;
}

void Service::setDomainsInternal(const QStringList &domains)
{
    m_domains = domains;
    Q_EMIT dataChanged();
}

QStringList Service::domainsConfiguration() const
{
    return m_domainsConfiguration;
}

void Service::setDomainsConfigurationInternal(const QStringList &domains)
{
    m_domainsConfiguration = domains;
    Q_EMIT dataChanged();
}

void Service::setDomainsConfiguration(const QStringList &domains)
{
    QDBusPendingReply<> reply = m_serviceInterface->SetProperty("Domains.Configuration",  QDBusVariant(domains));
    reply.waitForFinished();
    if (reply.isError())
        qDebug() << Q_FUNC_INFO << "error: " << reply.error();
}

EthernetData *Service::ethernet() const
{
    return m_ethernet;
}

EthernetData *Service::ethernetConfiguration() const
{
    return m_ethernetConfiguration;
}

IPV4Data *Service::ipv4() const
{
    return m_ipv4;
}

IPV4Data *Service::ipv4Configuration() const
{
    return m_ipv4Configuration;
}

IPV6Data *Service::ipv6() const
{
    return m_ipv6;
}

IPV6Data *Service::ipv6Configuration() const
{
    return m_ipv6Configuration;
}

ProxyData *Service::proxy() const
{
    return m_proxy;
}

ProxyData *Service::proxyConfiguration() const
{
    return m_proxyConfiguration;
}

ProviderData *Service::provider() const
{
    return m_provider;
}

ProviderData *Service::providerConfiguration() const
{
    return m_providerConfiguration;
}

void Service::connect()
{
    m_serviceInterface->Connect();
}

void Service::disconnect()
{
    m_serviceInterface->Disconnect();
}

void Service::remove()
{
    m_serviceInterface->Remove();
}

void Service::moveBefore(Service *service)
{
    m_serviceInterface->MoveBefore(service->objectPath());
}

void Service::moveAfter(Service *service)
{
    m_serviceInterface->MoveAfter(service->objectPath());
}

void Service::resetCounters()
{
    m_serviceInterface->ResetCounters();
}



EthernetData::EthernetData(Service *parent)
    : ConfigurableObject(parent),
      m_mtu(0),
      m_speed(0)
{
}

EthernetData::~EthernetData()
{
}

QString EthernetData::method() const
{
    return m_method;
}

void EthernetData::setMethod(const QString &method)
{
    m_method = method;
}

QString EthernetData::interface() const
{
    return m_interface;
}

void EthernetData::setInterface(const QString &interface)
{
    m_interface = interface;
}

QString EthernetData::address() const
{
    return m_address;
}

void EthernetData::setAddress(const QString &address)
{
    m_address = address;
}

quint16 EthernetData::mtu() const
{
    return m_mtu;
}

void EthernetData::setMtu(quint16 mtu)
{
    m_mtu = mtu;
}

quint16 EthernetData::speed() const
{
    return m_speed;
}

void EthernetData::setSpeed(quint16 speed)
{
    m_speed = speed;
}

QString EthernetData::duplex() const
{
    return m_duplex;
}

void EthernetData::setDuplex(const QString &duplex)
{
    m_duplex = duplex;
}

IPV4Data::IPV4Data(Service *parent)
    : ConfigurableObject(parent)
{
}

IPV4Data::~IPV4Data()
{
}

QString IPV4Data::method() const
{
    return m_method;
}

void IPV4Data::setMethod(const QString &method)
{
    m_method = method;
    if (m_method == QLatin1String("dhcp")) {
        m_address.clear();
	m_netmask.clear();
	m_gateway.clear();
    } 
}

QString IPV4Data::address() const
{
    return m_address;
}

void IPV4Data::setAddress(const QString &address)
{
    if (m_method != QLatin1String("dhcp"))
        m_address = address;
}

QString IPV4Data::netmask() const
{
    return m_netmask;
}

void IPV4Data::setNetmask(const QString &netmask)
{
    if (m_method != QLatin1String("dhcp"))
        m_netmask = netmask;
}

QString IPV4Data::gateway() const
{
    return m_gateway;
}

void IPV4Data::setGateway(const QString &gateway)
{
    if (m_method != QLatin1String("dhcp"))
        m_gateway = gateway;
}

IPV6Data::IPV6Data(Service *parent)
    : ConfigurableObject(parent)
{
}

IPV6Data::~IPV6Data()
{
}

QString IPV6Data::method() const
{
    return m_method;
}

void IPV6Data::setMethod(const QString &method)
{
    m_method = method;
}

QString IPV6Data::address() const
{
    return m_address;
}

void IPV6Data::setAddress(const QString &address)
{
    m_address = address;
}

QString IPV6Data::prefixLength() const
{
    return m_prefixLength;
}

void IPV6Data::setPrefixLength(const QString &prefixLength)
{
    m_prefixLength = prefixLength;
}

QString IPV6Data::gateway() const
{
    return m_gateway;
}

void IPV6Data::setGateway(const QString &gateway)
{
    m_gateway = gateway;
}

QString IPV6Data::privacy() const
{
    return m_privacy;
}

void IPV6Data::setPrivacy(const QString &privacy)
{
    m_privacy = privacy;
}

ProxyData::ProxyData(Service *parent)
    : ConfigurableObject(parent)
{
}

ProxyData::~ProxyData()
{
}

QString ProxyData::method() const
{
    return m_method;
}

void ProxyData::setMethod(const QString &method)
{
    m_method = method;
}

QString ProxyData::url() const
{
    return m_url;
}

void ProxyData::setUrl(const QString &url)
{
    m_url = url;
}

QStringList ProxyData::servers() const
{
    return m_servers;
}

void ProxyData::setServers(const QStringList &servers)
{
    m_servers = servers;
}

QStringList ProxyData::excludes() const
{
    return m_excludes;
}

void ProxyData::setExcludes(const QStringList &excludes)
{
    m_excludes = excludes;
}

ProviderData::ProviderData(Service *parent)
    : ConfigurableObject(parent)
{
}

ProviderData::~ProviderData()
{
}

QString ProviderData::host() const
{
    return m_host;
}

void ProviderData::setHost(const QString &host)
{
    m_host = host;
}

QString ProviderData::domain() const
{
    return m_domain;
}

void ProviderData::setDomain(const QString &domain)
{
    m_domain = domain;
}

QString ProviderData::name() const
{
    return m_name;
}

void ProviderData::setName(const QString &name)
{
    m_name = name;
}

QString ProviderData::type() const
{
    return m_type;
}

void ProviderData::setType(const QString &type)
{
    m_type = type;
}


QDebug operator<<(QDebug dbg, const Service *service)
{
    if (!service)
        return dbg << "invalid Service";
  
    dbg.nospace() << "[ " << service->objectPath().path().toLatin1().constData() << " ]" << endl;
    
    // first properties
    const QMetaObject *mo = service->metaObject();
    for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i) {
        QMetaProperty mp = mo->property(i);
        QVariant data = mp.read(service);

	if (data.isValid() && !data.isNull())
	    dbg << "\t" << mp.name() << " = " << data.toString().toLatin1().constData() << endl;
    }

    // now complex types
    foreach (QObject *child, service->children()) {
        ConfigurableObject *co = qobject_cast<ConfigurableObject*>(child);
        if (co) dbg.nospace() << "\t" << co;
    }
    
    return dbg.space();
}

QDebug operator<<(QDebug dbg, const ConfigurableObject *object)
{
    if (!object)
        return dbg << "invalid ConfigurableObject";

    dbg.nospace() << object->objectName().toLatin1().constData() << " = { ";
    const QMetaObject *mo = object->metaObject();
    for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i) {
      QMetaProperty mp = mo->property(i);
      QVariant data = mp.read(object);
      
      if (data.isValid() && !data.isNull())
          dbg.nospace() << mp.name() << "=" << data << " ";
    }
    dbg.nospace() << "}" << endl;
    
    return dbg.space();
}

